var CRange = Class.create();
CRange.prototype = 
{
  initialize: function( wind ) 
  {
    var selection = this.getElemSelection( wind );

    this.range = Try.these
    (
      function(){ return selection.getRangeAt(0) },  // FF
      function(){ return selection.createRange() }   // IE
    ) || false;

    this.doc = Element.extend( wind.document );
    this.container = Element.extend( this.getContainer() );

    this.r = Element.descendantOf( this.container, this.doc ); // is selection in this iframe?
//    alert(' r= ' + r )
//    if( !r ) // correct IE unpredectable behavior when selection not from the iframe
//    {
//      this.container = this.doc;
//      var e = this.doc.createElement( 'span' );
//      Element.extend( e );
//      this.container.appendChild( e );
//      this.range = this.getRangeForElement( e );
//    }
        
    this.compareType = Try.these
    (
      function(){ return [ Range.START_TO_START, Range.END_TO_START, Range.START_TO_END, Range.END_TO_END ] },  // FF
      function(){ return [ "StartToStart", "StartToEnd", "EndToStart", "EndToEnd" ] }   // IE
    ) || false;

    this.startOff = (this.r)? this.startOffset() : 0;
    this.endOff = (this.r)? this.endOffset() : 0;
  },

  getElemSelection: function( wind )
  {
    return Try.these
    (
      function(){ return wind.getSelection() },      // FF
      function(){ return wind.document.selection }   // IE
    ) || false;
  },

  getRangeForElement: function( domElement )
  {
    var _this = this;
    return Try.these
    (
      function()     // FF
      {
        var rangeToCheck = _this.doc.createRange();
        rangeToCheck.selectNode( domElement.firstChild );
        return rangeToCheck 
      },
      function()     // IE
      { 
        var rangeToCheck = _this.doc.body.createTextRange();
        rangeToCheck.moveToElementText( domElement );
        return rangeToCheck 
      }
    ) || false;
  },
/*
 position     this Range      
   type                       
            |             |   
    1    [--|-------------|--]
            |             |   
    2    [--|---].........|   
            |             |   
    3       |.[--------]..|   
            |             |   
    4       |.....[-------|--]
*/
  positionInSelection: function( domElement )
  {
    var rangeToCheck = this.getRangeForElement( domElement );
    if( this.compareBoundaryPoints( 0, rangeToCheck ) == 1 &&
        this.compareBoundaryPoints( 1, rangeToCheck ) == -1
       )
    {
      if( this.compareBoundaryPoints( 3, rangeToCheck ) == -1 )
        return 1;
      else
        return 2;  
    }

    if( this.compareBoundaryPoints( 0, rangeToCheck ) <= 0 && 
        this.compareBoundaryPoints( 2, rangeToCheck ) == 1 
      )
    {
      if( this.compareBoundaryPoints( 3, rangeToCheck ) == -1 
        )
      {
        return 4;
      }
      else
      {
        return 3;
      }    
    }
    return 0;
  },
  
  compareBoundaryPoints: function( type, rangeToCheck )
  {
    var r = this.range;
    var t = this;
    return Try.these
    (
      function(){ return r.compareBoundaryPoints( t.compareType[type], rangeToCheck ) },
      function(){ return r.compareEndPoints( t.compareType[type], rangeToCheck )      }
    ) || false;
  },
  
  getContainer: function()
  {
    var r = this.range, t;
    var container = Try.these
    (
      function(){ (t = r.commonAncestorContainer).hasChildNodes(); return t }, // FF
      function(){ (t = r.parentElement()).hasChildNodes(); return t }          // IE
    ) || false;
  
//    alert( '1) tag name: ' + container.tagName + '; id: ' + container.id )
    while( container && !(container.tagName == 'P' || container.tagName == 'BODY') )
    {
      container = container.parentNode
//    alert( 'i) tag name: ' + container.tagName + '; id: ' + container.id )
    }
//    alert( 'ret) tag name: ' + container.tagName + '; id: ' + container.id )
    return container;
  },
  
  getNodes: function()
  {
    if( !this.container )
      return elements;
  
    var last, elements = new Array(), p, elementToCheck, L;
    var childs = this.container.childNodes
    for( var i = 0; i < childs.length; i++ ) 
    {
      elementToCheck = childs[i];
      if( (p = this.positionInSelection( elementToCheck )) == 0 )
        continue;
      L = elementToCheck.innerHTML.unescapeHTML().replace(/&nbsp;/g,' ').length;
      switch( p )
      {
        case 1: elements.unshift( [1, elementToCheck, [this.startOff, this.endOff] ] ); break;
        case 2: elements.unshift( [2, elementToCheck, [this.startOff, L] ] ); break;
        case 3: elements.push( [3, elementToCheck, [0, L] ] ); break;
        case 4: last = [4, elementToCheck, [0, this.endOff] ]; break;
      }
    }
  
    if( last )
      elements.push( last );
    
    return elements;
  },
  
  getText: function()
  {
    var r = this.range;
    return Try.these
    (
      function(){ return r.toString() },
      function(){ return r.text }
    ) || '';
  },
  
  offsetDetermination: function( id )   // IE specific
  {
//    var markElem = $('mark0');
    var markElem = this.doc.getElementById( id );
    Element.extend( markElem );
    var siblings = markElem.parentNode.childNodes

    var string = '';
    for( var i = 0; i < siblings.length; i++ )
    {
      if( siblings[i].id == id )
        break;
      if( siblings[i].nodeType == 3 )
      {
        string += siblings[i].nodeValue;
      }
    }
//    alert( string )
    markElem.remove();
    return string.length;
  },
  
  startOffset: function()
  {
    var r = this.range, _this = this, t;
    return Try.these
    (
      function(){ (t = r.startOffset).toString(); return t },
      function()
      { 
        var rangeToMark = r.duplicate();
        rangeToMark.moveEnd("character", -(_this.getText().length) );
        rangeToMark.pasteHTML( '<span id="mark0">*</span>');
        return _this.offsetDetermination( 'mark0' );
      }
    ) || 0;
  },
  
  endOffset: function()
  {
    var r = this.range, _this = this, t;
    return Try.these
    (
      function(){ (t = r.endOffset).toString(); return t },
      function()
      { 
        var rangeToMark = r.duplicate();
        rangeToMark.moveStart("character", _this.getText().length );
        rangeToMark.pasteHTML( '<span id="mark1">*</span>');
        return _this.offsetDetermination( 'mark1' );
      }
    ) || 0;
  },
  
/*
 case   input        result   inherit 
       sequence     sequence   style  
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1    [1]        | {1}{N}{N}    Y    
  2    [2]        | {2}{N}       Y    
  3    [2][3]..   | {2}{N}            
  4    [2][3]..[4]| {2}{N}{4}         
  5    [2][4]     | {2}{N}{4}         
  6    [3]..      | {N}          Y/if single
  7    [3]..[4]   | {N}{4}            
  8    [4]        | {N}{4}       Y    
                                      
Notes: [3]s are always deleted        
*/
  getSelectedElement: function()
  {
    var STATE_0 = 0, STATE_1 = 1, STATE_2 = 2, STATE_3 = 3, STATE_4 = 4;
    var selText = this.getText();
    if( selText.length == 0 || ( this.startOff == 0 && this.endOff == 0 ) )
      return null;
    var elements = this.getNodes();
    var elementForProcessing, state = 0;
    
  for( var i = 0; i < elements.length; i++ )
  {
    if( state < 0 )
      break;
    var p = elements[i][0];
    var elem = elements[i][1];
    Element.extend( elem );
    var str = elem.innerHTML.unescapeHTML().replace(/&nbsp;/g,' ');
    var so = elements[i][2][0];
    var eo = elements[i][2][1];
    switch( p )
    {
      case 1:
        switch( state )
        {
          case STATE_0:
            var finishElem = this.createNewElement( str.substring( eo ), elem.nextSibling, elem.cloneNode(true) );
            this.editElement( str.substring( 0, so ), elem );
            elementForProcessing = this.createNewElement( selText, finishElem, elem.cloneNode(true) );
            state = 1;
            break;
          case STATE_1:
          case STATE_2:
          case STATE_3:
          case STATE_4:
            state = -1;
            break;
        }
        break;        
      case 2:
        switch( state )
        {
          case STATE_0:
            this.editElement( str.substring( 0, so ), elem );
            if( elements.length < 2 )
            {
              elementForProcessing = this.createNewElement( selText, elem.nextSibling, elem.cloneNode(true) )
            }
            state = 2;
            break;
          case STATE_1:
          case STATE_2:
          case STATE_3:
          case STATE_4:
            state = -1;
            break;
        }
        break;        
      case 3:
        switch( state )
        {
          case STATE_0:
            if( elements.length < 2 )
            {
              elementForProcessing = this.createNewElement( selText, elem, elem.cloneNode(true) );
              elem.remove();
              state = 3;
              break;
            }
          case STATE_2:
            elementForProcessing = this.createNewElement( selText, elem )
          case STATE_3:
            elem.remove();
            state = 3;
            break;
          case STATE_1:
          case STATE_4:
            state = -1;
            break;
        }
        break;        
      case 4:
        switch( state )
        {
          case STATE_0:
            elementForProcessing = this.createNewElement( selText, elem, elem.cloneNode(true) );
            this.editElement( str.substring( eo ), elem );
            state = 4;
            break;
          case STATE_2:
            elementForProcessing = this.createNewElement( selText, elem );
          case STATE_3:
            this.editElement( str.substring( eo ), elem );
            state = 4;
            break;
          case STATE_1:
          case STATE_4:
            state = -1
            break;
        }
        break;        
    }
  }
    return elementForProcessing;
  },
  
  createNewElement: function( text, elem, newEl )
  {
    var e;
    if( newEl )
    {
      e = newEl;  // if clone is used
      e.innerHTML = '';
    }
    else  
      e = this.doc.createElement( 'span' );
    Element.extend( e );
    e.appendChild( this.doc.createTextNode( text ) );
    this.container.insertBefore( e, elem );
    return e;
  },
  
  editElement: function( text, elem )
  {
    elem.innerHTML = ''; // in other way ( elem.innerHTML = text ), IE trims leading spaces...
    elem.appendChild( this.doc.createTextNode( text ) );
  }
}
